package com.nageoffer.jobtrain.admin.service.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.nageoffer.jobtrain.admin.common.biz.user.UserContext;
import com.nageoffer.jobtrain.admin.common.constant.RedisCacheConstant;
import com.nageoffer.jobtrain.admin.dao.entity.AdminUserDO;
import com.nageoffer.jobtrain.admin.dao.mapper.AdminUserMapper;
import com.nageoffer.jobtrain.admin.dto.req.AdminUserLoginReqDTO;
import com.nageoffer.jobtrain.admin.dto.req.AdminUserRegisterReqDTO;
import com.nageoffer.jobtrain.admin.dto.resp.AdminUserLoginRespDTO;
import com.nageoffer.jobtrain.admin.service.AdminUserService;
import com.nageoffer.jobtrain.common.toolkit.constant.JWTPlayLoadConstant;
import com.nageoffer.jobtrain.common.toolkit.utils.BCryptUtils;
import com.nageoffer.jobtrain.common.toolkit.utils.JWTUtils;
import com.nageoffer.jobtrain.common.web.exception.ClientException;
import com.nageoffer.jobtrain.common.web.result.Result;
import com.nageoffer.jobtrain.common.web.result.Results;
import lombok.RequiredArgsConstructor;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.dao.DuplicateKeyException;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Service;

import java.util.*;

import static com.nageoffer.jobtrain.common.web.errorcode.BaseErrorCode.USER_TOKEN_VALID_FAIL;

/**
 * 管理员用户接口实现层
 * author：唐晨
 */
@Service
@RequiredArgsConstructor
public class AdminUserServiceImpl extends ServiceImpl<AdminUserMapper, AdminUserDO> implements AdminUserService {
    private final AdminUserMapper adminUserMapper;

    private final RedissonClient redissonClient;
    private final StringRedisTemplate stringRedisTemplate;

    @Override
    public void registerAdminUser(AdminUserRegisterReqDTO requestParam) {
        RLock lock = redissonClient.getLock(RedisCacheConstant.LOCK_USER_REGISTER_KEY + requestParam.getUsername());
        if (!lock.tryLock()) {
            throw new ClientException("该管理员账户已存在");
        }
        try {
            requestParam.setPassword(BCryptUtils.encryptPassword(requestParam.getPassword()));
            int result = baseMapper.insert(BeanUtil.toBean(requestParam, AdminUserDO.class));
            if (result < 1) {
                throw new ClientException("新增管理员账户失败");
            }
        } catch (DuplicateKeyException ex) {
            // 此时主键冲突
            throw new ClientException("该管理员账户已存在");
        } finally {
            lock.unlock();
        }
    }

    @Override
    public AdminUserLoginRespDTO loginAdminUser(AdminUserLoginReqDTO requestParam) {
        LambdaQueryWrapper<AdminUserDO> queryWrapper = Wrappers.lambdaQuery(AdminUserDO.class)
                .eq(AdminUserDO::getUsername, requestParam.getUsername())
                .eq(AdminUserDO::getDelFlag, 0);
        AdminUserDO adminUserDO = adminUserMapper.selectOne(queryWrapper);
        if (adminUserDO == null) {
            throw new ClientException("管理员用户不存在");
        }

        if (!BCryptUtils.matchesPassword(requestParam.getPassword(), adminUserDO.getPassword())) {
            throw new ClientException("管理员用户密码输入错误");
        }

        // 查看缓存中是否存在该用户token，可能是二次登录，返回缓存中的token即可
        String loginToken = stringRedisTemplate.opsForValue().get(RedisCacheConstant.ADMIN_USER_LOGIN_KEY + requestParam.getUsername());
        if (!StrUtil.isEmpty(loginToken)) {
            return new AdminUserLoginRespDTO(loginToken);
        }

        // 生成 token返回给前端
        Map<String, Object> playLoad = new HashMap<>();
        playLoad.put(JWTPlayLoadConstant.LOGIN_PLAY_LOAD_ID, adminUserDO.getId());
        playLoad.put(JWTPlayLoadConstant.LOGIN_PLAY_LOAD_USERNAME, adminUserDO.getUsername());
        String token = JWTUtils.generateToken(playLoad);

        // 避免设置缓存成功，而设置缓存时间失败，中途宕机等情况 -使用Lua脚本进行设置
        String luaScript = "redis.call('SET', KEYS[1], ARGV[1]); " +
                "redis.call('EXPIRE', KEYS[1], ARGV[2]);" +
                " return 1;";
        List<String> keys = Collections.singletonList(RedisCacheConstant.ADMIN_USER_LOGIN_KEY + requestParam.getUsername());
        List<String> args = Arrays.asList(token, String.valueOf(JWTUtils.expire));

        // 执行Lua脚本
        stringRedisTemplate.execute(
                new DefaultRedisScript<>(luaScript, Long.class),
                keys,
                args.toArray()
        );
        return new AdminUserLoginRespDTO(token);
    }

    @Override
    public Result<String> getAdminUserName() {
        String token = stringRedisTemplate.opsForValue().get(RedisCacheConstant.ADMIN_USER_LOGIN_KEY + UserContext.getAdminUsername());
        if (token != null && JWTUtils.verify(token)) {
            return Results.success(UserContext.getAdminUsername());
        }
        return Results.failure(USER_TOKEN_VALID_FAIL);
    }

    @Override
    public Boolean logoutAdminUser() {
        String adminUserKey = RedisCacheConstant.ADMIN_USER_LOGIN_KEY + UserContext.getAdminUsername();
        return Optional.ofNullable(stringRedisTemplate.opsForValue().get(adminUserKey))
                .map(value -> stringRedisTemplate.delete(adminUserKey))
                .orElse(false);
    }
}
